import { beforeEach, describe, expect, mock, test } from "bun:test"; const ownerRecord = { id: 310001, publicSlug: "consistency-user", nickname: "Consistency", avatar: null, avatarVisibility: "public", bioVisibility: "public", bioMarkdown: "", socialLinksJson: "[]", status: "active", }; const selectMock = mock(() => ({ from: () => ({ where: () => ({ limit: async () => [ownerRecord], }), }), })); const parseSocialLinksJson = mock(() => []); const normalizePublicListPage = mock((pageRaw: unknown) => { const n = Number(pageRaw); return Number.isFinite(n) && n >= 1 ? Math.trunc(n) : 1; }); function publicTotal(rows: T[]) { return rows.filter((row) => row.visibility === "public").length; } const previewPosts = [ { slug: "preview-post-a", visibility: "public" }, { slug: "preview-post-b", visibility: "public" }, { slug: "preview-post-hidden", visibility: "private" }, ]; const previewTimeline = [ { id: 301, visibility: "public" }, { id: 302, visibility: "public" }, { id: 303, visibility: "private" }, ]; const previewReading = [ { id: 401, visibility: "public" }, { id: 402, visibility: "public" }, { id: 403, visibility: "unlisted" }, ]; const pagePosts = [ { slug: "page-post-1", visibility: "public" }, { slug: "page-post-2", visibility: "private" }, { slug: "page-post-3", visibility: "public" }, ]; const pageTimeline = [ { id: 501, visibility: "private" }, { id: 502, visibility: "public" }, { id: 503, visibility: "public" }, ]; const pageReading = [ { id: 601, visibility: "public" }, { id: 602, visibility: "unlisted" }, { id: 603, visibility: "public" }, ]; const getPublicPostsPreviewBySlug = mock(async () => ({ items: previewPosts.filter((row) => row.visibility === "public"), total: publicTotal(previewPosts), })); const getPublicTimelinePreviewBySlug = mock(async () => ({ items: previewTimeline.filter((row) => row.visibility === "public"), total: publicTotal(previewTimeline), })); const getPublicRssPreviewBySlug = mock(async () => ({ items: previewReading.filter((row) => row.visibility === "public"), total: publicTotal(previewReading), })); const getPublicPostsPageBySlug = mock(async () => ({ items: pagePosts.filter((row) => row.visibility === "public"), total: publicTotal(pagePosts), page: 1, pageSize: 10, })); const getPublicTimelinePageBySlug = mock(async () => ({ items: pageTimeline.filter((row) => row.visibility === "public"), total: publicTotal(pageTimeline), page: 1, pageSize: 10, })); const getPublicRssPageBySlug = mock(async () => ({ items: pageReading.filter((row) => row.visibility === "public"), total: publicTotal(pageReading), page: 1, pageSize: 10, })); mock.module("drizzle-pkg/lib/db", () => ({ dbGlobal: { select: selectMock, }, })); mock.module("drizzle-pkg/lib/schema/auth", () => ({ users: { id: "id", publicSlug: "publicSlug", status: "status", }, })); mock.module("drizzle-orm", () => ({ and: (...conditions: unknown[]) => conditions, eq: (a: unknown, b: unknown) => [a, b], })); mock.module("#server/service/profile", () => ({ parseSocialLinksJson, })); let realGetPublicHubBySlug: ((...args: unknown[]) => unknown) | undefined; mock.module("#server/service/public-hub", () => ({ getPublicHubBySlug: (...args: unknown[]) => { if (!realGetPublicHubBySlug) { throw new Error("realGetPublicHubBySlug not initialized"); } return realGetPublicHubBySlug(...args); }, })); mock.module("#server/service/posts", () => ({ getPublicPostsPreviewBySlug, getPublicPostsPageBySlug, })); mock.module("#server/service/timeline", () => ({ getPublicTimelinePreviewBySlug, getPublicTimelinePageBySlug, })); mock.module("#server/service/rss", () => ({ getPublicRssPreviewBySlug, getPublicRssPageBySlug, })); ({ getPublicHubBySlug: realGetPublicHubBySlug } = await import("../../../../service/public-hub")); mock.module("#server/utils/public-pagination", () => ({ normalizePublicListPage, })); mock.module("h3", () => ({ defineEventHandler: (handler: unknown) => handler, createError: (input: { statusCode: number; statusMessage: string }) => { const error = new Error(input.statusMessage) as Error & { statusCode: number; statusMessage: string; }; error.statusCode = input.statusCode; error.statusMessage = input.statusMessage; return error; }, getQuery: (event: { query?: Record }) => event.query ?? {}, })); globalThis.defineEventHandler ??= ((handler: unknown) => handler) as never; globalThis.createError ??= ((input: { statusCode: number; statusMessage: string }) => { const error = new Error(input.statusMessage) as Error & { statusCode: number; statusMessage: string; }; error.statusCode = input.statusCode; error.statusMessage = input.statusMessage; return error; }) as never; globalThis.getQuery ??= ((event: { query?: Record }) => event.query ?? {}) as never; globalThis.R ??= { success: (data: unknown) => data, }; const profileHandler = (await import("../[publicSlug].get")).default; const postsHandler = (await import("./posts/index.get")).default; const timelineHandler = (await import("./timeline/index.get")).default; const readingHandler = (await import("./reading/index.get")).default; describe("public profile totals consistency", () => { beforeEach(() => { selectMock.mockClear(); parseSocialLinksJson.mockClear(); getPublicPostsPreviewBySlug.mockClear(); getPublicTimelinePreviewBySlug.mockClear(); getPublicRssPreviewBySlug.mockClear(); getPublicPostsPageBySlug.mockClear(); getPublicTimelinePageBySlug.mockClear(); getPublicRssPageBySlug.mockClear(); normalizePublicListPage.mockClear(); }); test("同一 slug 下 hub.modules..total 与对应子页 total 一致(public-only)", async () => { const [profileRes, postsRes, timelineRes, readingRes] = await Promise.all([ profileHandler({ context: { params: { publicSlug: ownerRecord.publicSlug } } } as never), postsHandler({ context: { params: { publicSlug: ownerRecord.publicSlug } }, query: { page: "1" }, } as never), timelineHandler({ context: { params: { publicSlug: ownerRecord.publicSlug } }, query: { page: "1" }, } as never), readingHandler({ context: { params: { publicSlug: ownerRecord.publicSlug } }, query: { page: "1" }, } as never), ]); expect(profileRes.modules.posts.total).toBe(postsRes.total); expect(profileRes.modules.timeline.total).toBe(timelineRes.total); expect(profileRes.modules.reading.total).toBe(readingRes.total); expect(profileRes.modules.posts.total).toBe(profileRes.posts.total); expect(profileRes.modules.timeline.total).toBe(profileRes.timeline.total); expect(profileRes.modules.reading.total).toBe(profileRes.rssItems.total); expect(postsRes.total).toBe(2); expect(timelineRes.total).toBe(2); expect(readingRes.total).toBe(2); }); });